<?php
/**
 *  WB_PDOObjStore class
 *  
 *  Class which implements the storage of an
 *  objects representation through PHP PDO. 
 *  Used in conjunction with objects 
 *  of the StorableClass base class.
 *
 *  Use cases:
 *  - SAVE Object Y with expire timestamp of x, disposable Y/N
 *  - UPDATE (save) Object Y with modified & expire timestamps, disposable Y/N
 *  - RESTORE Object Y and get 'modified', 'expire' and 'disposable' fields
 *  - DELETE Object Y
 *
 *  @author Jean-Lou Dupont
 *  @version @@package-version@@
 */
/* 
 * SCHEMA
 * ======
 * 
		CREATE TABLE `objects` (
		  `id` varchar(255) collate latin1_general_ci NOT NULL,
		  `disposable` tinyint(1) NOT NULL,
		  `modified` datetime NOT NULL,
		  `expire` datetime NOT NULL,
		  `data` longblob NOT NULL,
		  `uid` int(11) NOT NULL,
		  PRIMARY KEY  (`id`),
		  UNIQUE KEY `uid` (`uid`),
		  KEY `modified` (`modified`),
		  KEY `expire` (`expire`)
		) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
 *
 */

//{{include
require_once 'web-bloks/DbObjStore/DbObjStore.php';
//include}} 

 class WB_PDOObjStore
 	extends WB_DbObjStore
 {
	static $schema = array(
		array( 'Field' => 'id',			'Type' => 'varchar(255)',	'Null' => 'NO', 'Key' => 'PRI' ),
		array( 'Field' => 'disposable',	'Type' => 'tinyint(1)',		'Null' => 'NO', 'Key' => '' ),
		array( 'Field' => 'modified',	'Type' => 'datetime', 		'Null' => 'NO', 'Key' => 'MUL' ),
		array( 'Field' => 'expire',		'Type' => 'datetime',		'Null' => 'NO', 'Key' => 'MUL' ),
		array( 'Field' => 'data', 		'Type' => 'longblob',		'Null' => 'NO', 'Key' => '' ),						
	);
 	/**
 	 * database handle
 	 */
 	var $dbh = null;
	
	/**
	 * last result set from PDO
	 */
	var $lastResult = null;
		
 // ======================================================
 // CONSTRUCTOR / DESTRUCTOR
 // ======================================================
 	/**
 	 * Constructor
 	 * $params = array(
 	 * 		'key'	=> 'object key name', // [optional] for BaseClass 
 	 * 		'dsn'	=> 'mysql:host=localhost;dbname=test',
 	 * 		'user'	=> 'user',
 	 * 		'pass'	=> 'password',
 	 * 		'table'	=> 'table_where_objects_are_stored'
 	 * );
 	 * 
 	 * @return 
 	 * @param $params Object
 	 */
	public function __construct( $params )
	{
		parent::__construct( $params );
		
	}
	public function __destruct()
	{
		$this->dbh = null;
	}

 // ======================================================
 // Inherited Methods from WB_BaseClass
 // ======================================================

	public function getClassPath()
	{
		return 'web-bloks/PDOObjStore';
	}
		
	public function getClassVersion()
	{
		return '$Id$';
	}
	
 // ======================================================
 // Inherited Methods from WB_DbObjStore
 // ======================================================
 	/**
 	 * checkSchema
 	 * 
 	 * @param $schema for derived classes
 	 * @throws WB_PDOObjStore_Exception
 	 */
	public function checkSchema( $schema = null )
	{
		try 
		{
			$rows = $this->lastResult = $this->dbh->query("SHOW COLUMNS FROM ".
										$this->params[ 'table' ]); 
		}
		catch( Exception $e )									
		{
			$this->throwFromException( $e );
		}

		// array can't be empty
		if (empty( $rows ))
			$this->throwException( 'schema error' );
					
		// match each row against an entry in the reference schema
		foreach( $rows as $row )										
			if ( !$this->matchRow( $schema, $row ) )
				$this->throwException( 'schema error' );				
	}
	/**
	 * matchRow
	 * 
	 * @param array
	 * @return boolean
	 */
	private function matchRow( &$schema, &$row )
	{
		$s = is_null( $schema ) ? self::$schema : $schema;
			
		// find the corresponding entry
		foreach( $s as &$entry )
		{
			$result = true;			
			foreach( array_keys( $entry ) as $key )
				if ( $row[ $key ] != $entry[ $key ] )
				{
					$result = false;
					break;
				}
			if ( $result == true )
				break;
		}
		
		return $result;
	}	
 // ======================================================
 // Interface 
 // ======================================================
 	/**
 	 * @see ObjStore::init
 	 */
	public function init()
	{
		$this->validateParams();
		
		try
		{
			$this->dbh = new PDO(	$this->params[ 'dsn' ], 
									$this->params[ 'user' ],
									$this->params[ 'pass' ],								
									array(	PDO::ATTR_PERSISTENT => true, 
											PDO::ERRMODE_EXCEPTION => true ));
		}
		catch(PDOException $e)
		{
			$this->throwFromException( $e );
		}
		
		// chainability
		return $this;
	}

	/**
	 * BEWARE: if saving an object that wasn't modified
	 *         (i.e. object being saved === object already in db)
	 *         PDO returns rowCount === 0
	 *         
	 * @see ObjStore::save
	 */	
	public function save( $identifier, &$obj )
	{
		$id = $identifier->get();

		$data = $obj->toXML();
		$modified = $obj->getModified();
		$expire = $obj->getExpire();
		$disposable = $obj->getDisposable();

		$result = null;
		$count = null;
		
		try 
		{
			$stmt = $this->dbh->prepare("UPDATE ".
										$this->params[ 'table' ].
										" SET id = ?, ".
											"modified=?, ".
											"expire=?, ".
											"disposable=?, ".
											"data=?"
										);
			$stmt->bindParam( 1, $id );										
			$stmt->bindParam( 2, $modified );			
			$stmt->bindParam( 3, $expire );			
			$stmt->bindParam( 4, $disposable );						
			$stmt->bindParam( 5, $data, PDO::PARAM_LOB );						
			
			$this->dbh->beginTransaction();
			$result = $stmt->execute();
			$count = $stmt->rowCount();
			$this->dbh->commit();
		}
		catch( Exception $e )									
		{
			$this->throwFromException( $e );
		}
		
		return ( $result === true) && ( $count > 0 );
	}
	
	/**
	 * Restores an object from the db store.
	 * If multiple objects with the same id exists,
	 * only return the first one whilst setting
	 * a status flag.
	 * 
	 * @see ObjStore::restore
	 * @todo unicity check
	 */	
	public function restore( $identifier )
	{
		$id = $identifier->get();
		$modified = null;
		$expire = null;
		$dispoable = null;
		$xml_data = null;
		try 
		{
			$stmt= $this->dbh->prepare("SELECT modified, expire, disposable, data".
										" FROM ".$this->params[ 'table' ]. 
										" WHERE id = '$id'");
			$stmt->execute();
			$stmt->bindColumn( 1, $modified );						
			$stmt->bindColumn( 2, $expire );						
			$stmt->bindColumn( 3, $disposable );						
			$stmt->bindColumn( 4, $xml_data, PDO::PARAM_LOB );
			$stmt->fetch( PDO::FETCH_BOUND );	
		}
		catch( Exception $e )									
		{
			$this->throwFromException( $e );
		}
		
		// go through the resulting PDOStatement object
		// and make sure we only return the first object...
		// assuming there can be multiple...
		// Set the 'unicity' flag accordingly.
		try 
		{
			//$unicity_flag = (count( $rows ) > 1)? false:true;
			//$xml_data = $row[ 'data' ];
			
			$obj = WB_StorableClass::fromXML( $xml_data );
			
			$obj->setModified( $modified );
			$obj->setExpire( $expire );			
			$obj->setDisposable( $disposable );			
			//$obj->setUnique( $unicity_flag );			
		}
		catch( Exception $e )
		{
			$this->throwFromException( $e );		
		}
		
		return $obj;		
	}
	
	/**
	 * @see ObjStore::delete
	 */	
	public function delete( $identifier )
	{
		$id = $identifier->get();
		try 
		{
			$result = $this->dbh->exec("DELETE FROM ".
										$this->params[ 'table' ]. 
										" WHERE id = '$id'");
		}
		catch( Exception $e )									
		{
			$this->throwFromException( $e );
		}
		return ( $result > 0) ? true:false;									
	}

	/**
	 * @see ObjStore::create
	 */	
	public function create( $identifier, &$obj )
	{
		$id = $identifier->get();

		$data = $obj->toXML();
		$modified = $obj->getModified();
		$expire = $obj->getExpire();
		$disposable = $obj->getDisposable();

		$count = null;
		$result = null;

		try 
		{
			$stmt = $this->dbh->prepare("INSERT INTO ".
										$this->params[ 'table' ].
										" SET id = ?, ".
											"modified=?, ".
											"expire=?, ".
											"disposable=?, ".
											"data=?"
										);
			$stmt->bindParam( 1, $id );										
			$stmt->bindParam( 2, $modified );			
			$stmt->bindParam( 3, $expire );			
			$stmt->bindParam( 4, $disposable );						
			$stmt->bindParam( 5, $data, PDO::PARAM_LOB );						
			
			$this->dbh->beginTransaction();
			$result = $stmt->execute();
			$count = $stmt->rowCount();			
			$this->dbh->commit();
		}
		catch( Exception $e )									
		{
			$this->throwFromException( $e );
		}
		return ( $result === true ) && ( $count === 1 );
	}
 
 } // end class definition
